package com.flyan.danmuhunter.bilibili.datafactory;

import cn.hutool.core.map.MapUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.flyan.danmuhunter.bilibili.api.Apis;
import com.flyan.danmuhunter.bilibili.config.Config;
import com.flyan.danmuhunter.bilibili.data.*;
import com.flyan.danmuhunter.bilibili.data.resp.LiveResult;
import com.flyan.danmuhunter.common.datafactory.LiveDataFactory;
import com.flyan.danmuhunter.common.model.ConsoleOutputDataHandler;
import com.flyan.danmuhunter.common.model.DataHandler;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.rmi.RemoteException;
import java.util.Arrays;
import java.util.stream.Collectors;

import static com.flyan.danmuhunter.common.utils.Helper._not_empty;
import static com.flyan.danmuhunter.common.utils.Helper._null;

/**
 * @author flyan
 * @version 1.0
 * @date 2022/6/8
 */
@Slf4j
public class LiveDataFactoryImpl
        extends LiveDataFactory {

    /**
     * 认证数据包
     */
    private final ByteBuffer authPackageData;

    /**
     * 心跳数据包
     */
    private final ByteBuffer heartbeatPackageData;

    /**
     * 弹幕处理器
     */
    private DataHandler<Danmu> danmuHandler = new ConsoleOutputDataHandler<>();

    /**
     * 礼物处理器
     */
    private DataHandler<Gift> giftHandler = new ConsoleOutputDataHandler<>();

    /**
     * 榜单处理器
     */
    private DataHandler<Rank> rankHandler = new ConsoleOutputDataHandler<>();

    public LiveDataFactoryImpl(Long roomId, int heartbeatInterval) throws URISyntaxException, IOException {
        super(new URI(Config.WSS_URL), heartbeatInterval);
        roomId = Apis.readRoomid(roomId);
        if(_null(roomId)) {
            throw new RemoteException("无法获取真实房间id，请检查网络。或者b站提供的api已过期。");
        }
        this.authPackageData = genAuthPackageData(roomId);
        this.heartbeatPackageData = genHeartbeatPackageData();
    }


    public LiveDataFactoryImpl(Long roomId,
                               int heartbeatInterval,
                               DataHandler<Danmu> danmuHandler,
                               DataHandler<Gift> giftHandler,
                               DataHandler<Rank> rankHandler) throws URISyntaxException, IOException {
        this(roomId, heartbeatInterval);
        this.danmuHandler = danmuHandler;
        this.giftHandler = giftHandler;
        this.rankHandler = rankHandler;
    }

    @Override
    protected boolean auth() {
        this.send(authPackageData);
        return true;
    }

    @Override
    protected void heartbeat() {
        this.send(heartbeatPackageData.position(0));
        log.info("Heartbeat ok.");
    }

    @Override
    public void onClose(int i, String s, boolean b) {
        log.info("LiveDataFactory closed.");
    }

    @Override
    public void onError(Exception e) {
        log.error("LiveDataFactory catch exception: " + e.toString());
//        this.close();
    }

    /**
     * 鉴于 b 站回来的弹幕数据是二进制(可能加密)数据，故该方法并没有什么用，具体的回调应用 {@link #onMessage(ByteBuffer bytes)}，
     * 它在这的原因是但此方法被要求必须重写...
     */
    @Override
    public void onMessage(String message) {  }

    @Override
    public void onMessage(ByteBuffer bytes) {
        handleResult(new LiveResult(bytes));
    }

    /**
     * 构建组合认证数据包
     *
     * @return 数据包
     */
    private ByteBuffer genAuthPackageData(Long roomId) {
        String authJson = JSON.toJSONString(MapUtil.<String, Object>builder()
                .put("uid", Config.UID_VISITOR)
                .put("roomid", roomId)
                .put("protover", 1)
                .put("platform", "web")
                .put("clientver", "1.4.0")
                .build());
        byte[] bytes = authJson.getBytes(StandardCharsets.UTF_8);
        Header header = new Header(bytes.length + Header.HEADER_SIZE, Header.HEADER_SIZE,
                Config.PROTOCOL_VERSION, Op.OpType.CLIENT_AUTH_JOIN_ROOM.getCode(), 1);
        byte[] ans = new byte[bytes.length + Header.HEADER_SIZE];
        System.arraycopy(header.getByteBuffer().array(), 0, ans, 0, Header.HEADER_SIZE);
        System.arraycopy(bytes, 0, ans, Header.HEADER_SIZE, bytes.length);
        return ByteBuffer.wrap(ans);
    }

    /**
     * 构建心跳数据包
     *
     * @return 数据包
     */
    private ByteBuffer genHeartbeatPackageData() {
        return new Header(Header.HEADER_SIZE, Header.HEADER_SIZE,
                Config.PROTOCOL_VERSION, Op.OpType.CLIENT_HEARTBEAT.getCode(),
                1).getByteBuffer();
    }

    /**
     * 处理直播服务结果
     *
     * @param result 直播服务器返回的结果
     */
    private void handleResult(LiveResult result) {
        if (result == null) {
            return;
        }

        // System.out.println(result);
        Op.OpType opType = result.getHeader().getOp().getOpType();
        switch (opType) {
            /* 人气值 */
            case POPULAR_VAL:
                log.info("当前主播人气值: " + result.getBody());
                break;

            /* 命令 */
            case CMD:
                if(_not_empty(result.getCmdData())) {
                    /* 可能会同时有多个数据发过来，故要循环 */
                    for (JSONObject cmdDatum : result.getCmdData()) {
                        String cmdStr = cmdDatum.getString(Cmd.CMD);
                        if(!Arrays.stream(Cmd.CmdType.values()).map(Cmd.CmdType::name).collect(Collectors.toSet()).contains(cmdStr)) {
                            continue;
                        }

                        Cmd.CmdType cmd = Cmd.CmdType.valueOf(cmdStr);
                        switch (cmd) {
                            /* 弹幕消息 */
                            case DANMU_MSG:
                                JSONArray info = cmdDatum.getJSONArray("info");
                                UserInfo userInfo = new UserInfo(info.getJSONArray(2));
                                Danmu danmu = new Danmu(userInfo, info.getString(1));
                                danmuHandler.handle(danmu);
                                break;

                            /* 有人送礼物 */
                            case SEND_GIFT:
                                Gift gift = cmdDatum.getJSONObject("data").toJavaObject(Gift.class);
                                giftHandler.handle(gift);
                                break;

                            /* 在线榜单（如果出现在这里表明有榜单发生改变） */
                            case ONLINE_RANK_V2:
                                Rank rank = cmdDatum.getJSONObject("data").toJavaObject(Rank.class);
                                rankHandler.handle(rank);
                                break;

                            /* 有人进入直播 */
                            case INTERACT_WORD:
                                JSONObject data = cmdDatum.getJSONObject("data");
                                log.info(data.getString("uname") + "进入直播");
                                break;

                            default:
                                log.info(cmdDatum.toJSONString());
                                break;
                        }

                    }
                }

            default:
                break;
        }
    }


}
